
#include <BOpenGL>
#include <BRender>
#include <BMesh>
#include "member_BMesh.h"
#include "member_BSurface.h"
#include "member_BTerrain.h"
#include "member_BGeometry.h"

using namespace BWE;

#define member					(*(member_BMesh*)_ptr)
#define member_allocate()		_ptr = new member_BMesh(this)
#define member_release()		delete (member_BMesh*)_ptr

BMesh::BMesh(const BString& name)
{
	member_allocate();
	this->setName(name);
}
BMesh::BMesh(const BMesh& other) : BShape(other)
{
	member_allocate();
	this->setName(other.name());
	this->copy(&other);
}
BMesh::~BMesh()
{
	member_release();
}

void BMesh::copy(const BShape* other)
{
	BShape::copy(other);
	if (const BMesh* mesh = dynamic_cast<const BMesh*>(other))
	{
		mesh->flush();
		member.vertices = mesh_member(mesh)->vertices;
		member.normals = mesh_member(mesh)->normals;
		member.colors = mesh_member(mesh)->colors;
		member.coords = mesh_member(mesh)->coords;
		member.faces = mesh_member(mesh)->faces;
	}
	if (const BGeometry* geometry = dynamic_cast<const BGeometry*>(other))
	{
		geometry->flush();
		member.vertices = geometry_member(geometry)->vertices;
		member.normals = geometry_member(geometry)->normals;
		member.colors = geometry_member(geometry)->colors;
		member.coords = geometry_member(geometry)->coords;
		member.faces = new BFaceArray();
		for (int e = 0; e < geometry_member(geometry)->elements.size(); e++)
		{
			BElement* element = geometry_member(geometry)->elements[e];
			if (const BIntArray* indices = element->indices())
			{
				if (element->primitive() == Primitive_Triangles)
				{
					for (int i = 0; i < indices->size(); i += 3)
					{
						int index0 = indices->at(i + 0);
						int index1 = indices->at(i + 1);
						int index2 = indices->at(i + 2);
						member.faces->append(BFace(index0, index1, index2));
					}
					continue;
				}
				if (element->primitive() == Primitive_TriangleStrip)
				{
					for (int i = 0; i < indices->size(); i++)
					{
						int index0 = indices->at(i + 0);
						int index1 = indices->at(i + 1);
						int index2 = indices->at(i + 2);
						member.faces->append(BFace(index0, index1, index2));
					}
					continue;
				}
				if (element->primitive() == Primitive_Quads)
				{
					for (int i = 0; i < indices->size(); i += 4)
					{
						int index0 = indices->at(i + 0);
						int index1 = indices->at(i + 1);
						int index2 = indices->at(i + 2);
						int index3 = indices->at(i + 3);
						member.faces->append(BFace(index0, index1, index2));
						member.faces->append(BFace(index0, index2, index3));
					}
					continue;
				}
				if (element->primitive() == Primitive_QuadStrip)
				{
					for (int i = 0; i < indices->size(); i += 2)
					{
						int index0 = indices->at(i + 0);
						int index1 = indices->at(i + 1);
						int index2 = indices->at(i + 3);
						int index3 = indices->at(i + 2);
						member.faces->append(BFace(index0, index1, index2));
						member.faces->append(BFace(index0, index2, index3));
					}
					continue;
				}
				continue;
			}
			if (const BPieceArray* pieces = element->pieces())
			{
				if (element->primitive() == Primitive_Triangles)
				{
					for (int i = 0; i < pieces->size(); i += 3)
					{
						int index0 = pieces->at(i + 0).v();
						int index1 = pieces->at(i + 1).v();
						int index2 = pieces->at(i + 2).v();
						member.faces->append(BFace(index0, index1, index2));
					}
					continue;
				}
				if (element->primitive() == Primitive_TriangleStrip)
				{
					for (int i = 0; i < pieces->size(); i++)
					{
						int index0 = pieces->at(i + 0).v();
						int index1 = pieces->at(i + 1).v();
						int index2 = pieces->at(i + 2).v();
						member.faces->append(BFace(index0, index1, index2));
					}
					continue;
				}
				if (element->primitive() == Primitive_Quads)
				{
					for (int i = 0; i < pieces->size(); i += 4)
					{
						int index0 = pieces->at(i + 0).v();
						int index1 = pieces->at(i + 1).v();
						int index2 = pieces->at(i + 2).v();
						int index3 = pieces->at(i + 3).v();
						member.faces->append(BFace(index0, index1, index2));
						member.faces->append(BFace(index0, index2, index3));
					}
					continue;
				}
				if (element->primitive() == Primitive_QuadStrip)
				{
					for (int i = 0; i < pieces->size(); i += 2)
					{
						int index0 = pieces->at(i + 0).v();
						int index1 = pieces->at(i + 1).v();
						int index2 = pieces->at(i + 3).v();
						int index3 = pieces->at(i + 2).v();
						member.faces->append(BFace(index0, index1, index2));
						member.faces->append(BFace(index0, index2, index3));
					}
					continue;
				}
				continue;
			}
			int first = element->first();
			int count = element->count();
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = first; i < count; i += 3)
				{
					int index0 = i + 0;
					int index1 = i + 1;
					int index2 = i + 2;
					member.faces->append(BFace(index0, index1, index2));
				}
				continue;
			}
			if (element->primitive() == Primitive_TriangleStrip)
			{
				for (int i = first; i < count; i++)
				{
					int index0 = i + 0;
					int index1 = i + 1;
					int index2 = i + 2;
					member.faces->append(BFace(index0, index1, index2));
				}
				continue;
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = first; i < count; i += 4)
				{
					int index0 = i + 0;
					int index1 = i + 1;
					int index2 = i + 2;
					int index3 = i + 3;
					member.faces->append(BFace(index0, index1, index2));
					member.faces->append(BFace(index0, index2, index3));
				}
				continue;
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = first; i < count; i += 2)
				{
					int index0 = i + 0;
					int index1 = i + 1;
					int index2 = i + 3;
					int index3 = i + 2;
					member.faces->append(BFace(index0, index1, index2));
					member.faces->append(BFace(index0, index2, index3));
				}
				continue;
			}
		}
	}
	if (const BSurface* surface = dynamic_cast<const BSurface*>(other))
	{
		surface->flush();
		member.vertices = surface_member(surface)->vertices;
		member.normals = surface_member(surface)->normals;
		member.colors = surface_member(surface)->colors;
		member.coords.clear();
		member.coords.append(surface_member(surface)->coords);
		const BIntArray* indices = surface->indices();
		member.faces = new BFaceArray();
		member.faces->reserve(indices->size() / 2);
		for (int i = 0; i < indices->size(); i += 4)
		{
			int index0 = indices->at(i + 0);
			int index1 = indices->at(i + 1);
			int index2 = indices->at(i + 2);
			int index3 = indices->at(i + 3);
			member.faces->append(BFace(index0, index1, index2));
			member.faces->append(BFace(index0, index2, index3));
		}
	}
	if (const BTerrain* terrain = dynamic_cast<const BTerrain*>(other))
	{
		terrain->flush();
		member.vertices = terrain_member(terrain)->vertices;
		member.normals = terrain_member(terrain)->normals;
		member.colors = terrain_member(terrain)->colors;
		member.coords.clear();
		member.coords.append(terrain_member(terrain)->coords);
		const BIntArray* indices = terrain->indices();
		member.faces = new BFaceArray();
		member.faces->reserve(indices->size() / 2);
		for (int i = 0; i < indices->size(); i += 4)
		{
			int index0 = indices->at(i + 0);
			int index1 = indices->at(i + 1);
			int index2 = indices->at(i + 2);
			int index3 = indices->at(i + 3);
			member.faces->append(BFace(index0, index1, index2));
			member.faces->append(BFace(index0, index2, index3));
		}
	}
	if (member.faceOctTree)
	{
		member.freshFaceOctTree();
	}
}
BShape* BMesh::clone() const
{
	BMesh* mesh = new BMesh(*this);
	return mesh;
}

void BMesh::setOptimized(bool optimized)
{
	if (optimized && member.faceOctTree.empty())
	{
		member.faceOctTree = new FaceInfoOctTree();
		member.freshFaceOctTree();
		return;
	}
	if (!optimized && !member.faceOctTree.empty())
	{
		member.faceOctTree = 0;
		return;
	}
}
bool BMesh::optimized() const
{
	return !member.faceOctTree.empty();
}

void BMesh::setDataPolicy(Policy dataPolicy)
{
	if (member.dataPolicy != dataPolicy)
	{
		member.dataPolicy = dataPolicy;
		this->dirty();
	}
}
Policy BMesh::dataPolicy() const
{
	return member.dataPolicy;
}

void BMesh::setVertices(BVectorArray* vertices)
{
	if (member.vertices != vertices)
	{
		member.vertices = vertices;
		this->dirty();
	}
}
const BVectorArray* BMesh::vertices() const
{
	return member.vertices;
}
BVectorArray* BMesh::vertices()
{
	return member.vertices;
}

void BMesh::setNormals(BVectorArray* normals)
{
	if (member.normals != normals)
	{
		member.normals = normals;
		this->dirty();
	}
}
const BVectorArray* BMesh::normals() const
{
	return member.normals;
}
BVectorArray* BMesh::normals()
{
	return member.normals;
}

void BMesh::setColors(BColorArray* colors)
{
	if (member.colors != colors)
	{
		member.colors = colors;
		this->dirty();
	}
}
const BColorArray* BMesh::colors() const
{
	return member.colors;
}
BColorArray* BMesh::colors()
{
	return member.colors;
}

bool BMesh::setTexCoords(BCoordArray* texCoords, int unit)
{
	if (unit > 30)
		return false;
	if (member.coords.size() < unit + 1)
		member.coords.resize(unit + 1);
	member.coords[unit] = texCoords;
	for (int i = member.coords.size(); i > 0; i--)
	{
		if (member.coords[i - 1])
		{
			member.coords.remove(i, member.coords.size());
			break;
		}
	}
	this->dirty();
	return true;
}
const BCoordArray* BMesh::texCoords(int unit) const
{
	return member.coords(unit);
}
BCoordArray* BMesh::texCoords(int unit)
{
	return member.coords(unit);
}

void BMesh::setFaces(BFaceArray* faces)
{
	if (member.faces != faces)
	{
		member.faces = faces;
		this->dirty();
	}
}
const BFaceArray* BMesh::faces() const
{
	return member.faces;
}
BFaceArray* BMesh::faces()
{
	return member.faces;
}

bool BMesh::triangle(int index, BTriangle& triangle) const
{
	if (member.faces)
	{
		if (!member.faces->check(index))
			return false;
		const BFace& face = member.faces->at(index);
		triangle.a() = member.vertices->at(face.a());
		triangle.b() = member.vertices->at(face.b());
		triangle.c() = member.vertices->at(face.c());
		return true;
	}
	index *= 3;
	if (index >= 0 && index <= member.vertices->size() - 3)
	{
		triangle.a() = member.vertices->at(index);
		triangle.b() = member.vertices->at(index + 1);
		triangle.c() = member.vertices->at(index + 2);
		return true;
	}
	return false;
}
BTriangle BMesh::triangle(int index) const
{
	BTriangle temp;
	this->triangle(index, temp);
	return temp;
}

bool BMesh::empty() const
{
	return member.vertices == 0;
}
void BMesh::smooth()
{
	BVectorArray* vertices = member.vertices;
	if (!vertices)
		return;
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != vertices->size())
		normals = new BVectorArray(vertices->size());
	normals->fill(BVector());
	if (BFaceArray* faces = member.faces)
	{
		for (int i = 0; i < faces->size(); i++)
		{
			BFace& face = (*faces)[i];
			const BVector& va = (*vertices)[face.a()];
			const BVector& vb = (*vertices)[face.b()];
			const BVector& vc = (*vertices)[face.c()];
			BVector normal = (vb - va).cross(vc - va);
			if (normal.normalize())
			{
				(*normals)[face.a()] += normal;
				(*normals)[face.b()] += normal;
				(*normals)[face.c()] += normal;
			}
		}
		for (int i = 0; i < normals->size(); i++)
		{
			(*normals)[i].normalize();
		}
	}
	else
	{
		for (int i = 0; i < vertices->size(); i += 3)
		{
			const BVector& va = (*vertices)[i];
			const BVector& vb = (*vertices)[i + 1];
			const BVector& vc = (*vertices)[i + 2];
			BVector normal = (vb - va).cross(vc - va);
			if (normal.normalize())
			{
				(*normals)[i] = normal;
				(*normals)[i + 1] = normal;
				(*normals)[i + 2] = normal;
			}
		}
	}
	this->setNormals(normals);
}
void BMesh::reverse()
{
	if (member.vertices.empty())
		return;

	if (BFaceArray* faces = member.faces)
	{
		int size = member.faces->size();
		for (int i = 0; i < size; i++)
		{
			BFace& face = (*faces)[i];
			bSwap(face.b(), face.c());
		}
	}
	else
	{
		int size = member.vertices->size();
		for (int i = 0; i < size; i += 3)
		{
			BVector& vb = (*member.vertices)[i + 1];
			BVector& vc = (*member.vertices)[i + 2];
			bSwap(vb, vc);
		}
	}
	if (BVectorArray* normals = member.normals)
	{
		for (int i = 0; i < normals->size(); i++)
		{
			(*normals)[i] = -(*normals)[i];
		}
	}
	this->dirty();
}
void BMesh::simplify()
{
	if (member.faces)
	{
		BArray<int> indices(-1, member.vertices->size());
		for (int i = 0; i < member.faces->size(); i++)
		{
			const BFace& face = member.faces->at(i);
			indices[face.a()] = face.a();
			indices[face.b()] = face.b();
			indices[face.c()] = face.c();
		}
		BVectorArray* vertices = new BVectorArray();
		BVectorArray* normals = 0;
		if (member.normals)
			normals = new BVectorArray();
		BColorArray* colors = 0;
		if (member.colors)
			colors = new BColorArray();
		BCoordArray* coords = 0;
		if (member.coords.size())
			coords = new BCoordArray();
		for (int i = 0; i < indices.size(); i++)
		{
			int index = indices[i];
			if (index >= 0)
			{
				indices[i] = vertices->size();
				vertices->append(member.vertices->at(index));
				if (normals)
					normals->append(member.normals->at(index));
				if (colors)
					colors->append(member.colors->at(index));
				if (coords)
					coords->append(member.coords[0]->at(index));
			}
		}
		member.vertices = vertices;
		if (normals)
			member.normals = normals;
		if (colors)
			member.colors = colors;
		if (coords)
			member.coords[0] = coords;
		for (int i = 0; i < member.faces->size(); i++)
		{
			BFace& face = member.faces->at(i);
			face.a() = indices[face.a()];
			face.b() = indices[face.b()];
			face.c() = indices[face.c()];
		}
		this->dirty();
	}
}

bool BMesh::flush() const
{
	if (BShape::flush())
	{
		if (member.faceOctTree)
		{
			member.freshFaceOctTree();
		}
		return true;
	}
	return false;
}
void BMesh::clear()
{
	member.vertices = 0;
	member.normals = 0;
	member.faces = 0;
	member.colors = 0;
	member.coords.clear();
	this->dirty();
}

bool BMesh::query(const BString& text, BValue& value) const
{
	if (text == "facetree")
	{
		value = member.faceOctTree;
		return true;
	}
	if (text == "vertices")
	{
		value = member.vertices;
		return true;
	}
	return BShape::query(text, value);
}

BVector BMesh::inertia(BReal mass) const
{
	const BBox& box = this->box();
	BVector dimension = box.veca() + box.vecb() + box.vecc();

	BReal lx = 2 * dimension.x();
	BReal ly = 2 * dimension.y();
	BReal lz = 2 * dimension.z();

	BReal inex = mass / 12 * (ly * ly + lz * lz);
	BReal iney = mass / 12 * (lx * lx + lz * lz);
	BReal inez = mass / 12 * (lx * lx + ly * ly);

	return BVector(inex, iney, inez);
}
BVector BMesh::vertex(const BVector& direct) const
{
	struct SupportingCallback : public ITriangleCallback
	{
		const BVector& direct;
		BLine line;
		BReal maxRatio = 0;
		int count = 0;
		SupportingCallback(const BVector& dir, BReal length) : direct(dir)
		{
			line.setEnd(direct * length * 2);
		}
		virtual void process(const BTriangle& triangle, int triangleIndex)
		{
			count++;
			BReal ratio;
			if (line.intersect(triangle, ratio, true))
			{
				if (ratio > maxRatio)
				{
					maxRatio = ratio;
				}
			}
		}
	};
	SupportingCallback callback(direct, this->box().radius());
	member.overlapping(&callback, callback.line);
	return callback.line.vertex(callback.maxRatio);
}

void BMesh::refresh(BBox& box)
{
	member.localSpace.reset();
	if (member.vertices && member.vertices->size())
	{
		member.localSpace.reset(member.vertices->first());
		for (int i = 1; i < member.vertices->size(); i++)
		{
			member.localSpace.expand((*member.vertices)[i]);
		}
	}
	box = member.localSpace;
}
void BMesh::render(BRender& render)
{
	if (!member.vertices || member.vertices->empty())
		return;

	if (member.vertices)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		BOpenGL::glVertexArray(member.vertices);
	}

	if (member.normals)
	{
		glEnableClientState(GL_NORMAL_ARRAY);
		BOpenGL::glNormalArray(member.normals);
	}

	if (member.colors)
	{
		glEnableClientState(GL_COLOR_ARRAY);
		glColorPointer(4, GL_UNSIGNED_BYTE, 0, member.colors->data());
	}

	for (int i = 0; i < member.coords.size(); i++)
	{
		if (member.coords[i] && this->texture(i))
		{
			glClientActiveTexture(GL_TEXTURE0 + i);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			glTexCoordPointer(2, GL_FLOAT, 0, member.coords[i]->data());
		}
	}

	if (member.faces)
	{
		glEnableClientState(GL_INDEX_ARRAY);
		glDrawElements(GL_TRIANGLES, member.faces->size() * 3, GL_UNSIGNED_INT, member.faces->data());
		glDisableClientState(GL_INDEX_ARRAY);
	}
	else
	{
		glDrawArrays(GL_TRIANGLES, 0, member.vertices->size());
	}

	if (member.vertices)
	{
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	if (member.normals)
	{
		glDisableClientState(GL_NORMAL_ARRAY);
	}
	if (member.colors)
	{
		glDisableClientState(GL_COLOR_ARRAY);
	}
	for (int i = 0; i < member.coords.size(); i++)
	{
		if (member.coords[i] && this->texture(i))
		{
			glClientActiveTexture(GL_TEXTURE0 + i);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}
	}
}
